package com.dualsportmaps.android;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import net.osmand.osm.MapUtils;

import com.dualsportmaps.android.ConvertGPX.PolylineEncoder;

import android.graphics.RectF;


public class EncodedPolyline implements Iterable<SimpleTrackpoint> {
	private String color;
	private ArrayList<SimpleTrackpoint> trackPoints;
	public ArrayList<SimpleTrackpoint> markerPoints;
	public ArrayList<Double> distancePoints;
	private ArrayList<Integer> levels;
	public float bound1Lat; 
	public float bound1Lng;
	public float bound2Lat;
	public float bound2Lng;
	
	/**
	 * Polyline constructor that stores the given encodedPolyline and number of
	 * levels that follows Google's encoding standards.
	 * 
	 * @param encodedPolyline
	 * @param encodedLevels
	 */
	public EncodedPolyline(String encodedPolyline, int length) {
		this.trackPoints = new ArrayList<SimpleTrackpoint>();
		this.levels = new ArrayList<Integer>();
		StringBuilder sb = new StringBuilder();

		// The encodedLevels is a string of "B" of size length.
		for (int i = 0; i < length; i++) {
			sb.append("B");
		}
		this.decodeLine(encodedPolyline);
		this.decodeLevels(sb.toString());
	}

	/**
	 * Polyline constructor that stores the given encodedPolyline and
	 * encodedLevels that follows Google's encoding standards.
	 * 
	 * @param encodedPolyline
	 * @param encodedLevels
	 */
	public EncodedPolyline(String encodedPolyline, String encodedLevels) {
		this.trackPoints = new ArrayList<SimpleTrackpoint>();
		this.levels = new ArrayList<Integer>();
		this.decodeLine(encodedPolyline);
		this.decodeLevels(encodedLevels);
	}

	public EncodedPolyline(List<SimpleTrackpoint> trackPoints) {
		String encodedPolyline;
		String encodedLevels;
		HashMap<String,String> track = new PolylineEncoder().dpEncode(trackPoints);
		encodedPolyline = track.get("encodedPoints").toString();
		encodedLevels = track.get("encodedLevels").toString();
		this.trackPoints = new ArrayList<SimpleTrackpoint>();
		this.levels = new ArrayList<Integer>();
		this.decodeLine(encodedPolyline);
		this.decodeLevels(encodedLevels);
	}
	
	/**
	 * Polyline constructor that stores the given encodedPolyline and
	 * encodedLevels that follows Google's encoding standards.
	 * 
	 * @param encodedPolyline
	 * @param encodedLevels
	 * @param color
	 */
	public EncodedPolyline(String encodedPolyline, String encodedLevels, String color) {
		this.trackPoints = new ArrayList<SimpleTrackpoint>();
		this.levels = new ArrayList<Integer>();
		this.color = color;
		this.decodeLine(encodedPolyline);
		this.decodeLevels(encodedLevels);
	}


	public ArrayList<SimpleTrackpoint> getAllTrackPoints() {
		return trackPoints;
	}
	private Hashtable<Integer,ArrayList<SimpleTrackpoint>> levelsCache = new Hashtable<Integer,ArrayList<SimpleTrackpoint>>(); 
	public ArrayList<SimpleTrackpoint> getTrackPoints(int i) {
		int cacheNum = 18-i;
		if(levelsCache.containsKey(cacheNum)) {
			return levelsCache.get(cacheNum);
		}
		if(trackPoints.size() == levels.size()) {
			ArrayList<SimpleTrackpoint> retPoints = new ArrayList<SimpleTrackpoint>();
			for(int x =0; x<levels.size(); x++) {
				if(levels.get(x) >= cacheNum) {
					retPoints.add(trackPoints.get(x));
				}
			}
			levelsCache.put(cacheNum, retPoints);
			return retPoints;
		} else {
			return trackPoints;
		}
	}
	public boolean hasTrackPointsInRect(int i, RectF rect) {
		RectF nr = new RectF(rect);
		nr.sort();
		ArrayList<SimpleTrackpoint> points = getTrackPoints(i);
		for (SimpleTrackpoint point : points) {
			if (nr.contains(point.getLongitude(), point.getLatitude()))
				return true;
		}
		return false;
	}
	/**
	 * @return an iterator for the list of GeoPoints.
	 */
	public Iterator<SimpleTrackpoint> iterator() {
		PolylineIterator p = new PolylineIterator(trackPoints);
		return p;
	}

	/**
	 * Iterator class that iterates through the list of GeoPoints.
	 * 
	 * @author Michael Eng
	 * 
	 */
	public class PolylineIterator implements Iterator<SimpleTrackpoint> {
		private int index;
		private ArrayList<SimpleTrackpoint> coordinates;

		/**
		 * Constructor for PolylineIterator. Takes list of GeoPoints for future
		 * use for iteration.
		 * 
		 * @param coordinates
		 */
		public PolylineIterator(ArrayList<SimpleTrackpoint> coordinates) {
			index = 0;
			this.coordinates = coordinates;
		}

		/**
		 * @return true if there is an available GeoPoint.
		 */
		public boolean hasNext() {
			return index < coordinates.size();
		}

		/**
		 * @returns the next GeoPoint in the list.
		 */
		public SimpleTrackpoint next() {
			if (hasNext()) {
				return coordinates.get(index++);
			} else {
				throw new NoSuchElementException();
			}
		}

		/**
		 * This method is not supported in the implementation.
		 */
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}

	/**
	 * This function is from Google's polyline utility written in Javascript
	 * that I rewrote in Java. Decodes the class' encodedPolyline and stores the
	 * GeoPoints in the list of coordinates.
	 */
	private void decodeLine(String encodedPolyline) {
		markerPoints = new ArrayList<SimpleTrackpoint>();
		distancePoints = new ArrayList<Double>();
		int len = encodedPolyline.length();
		int index = 0;
		int lat = 0;
		int lng = 0;
		int minlat = 999999999;
		int minlng = 999999999;
		int maxlat = -999999999;
		int maxlng = -999999999;
		double dist = 0;
		double ldist = 0;
		float lflat = -999;
		float lflng = -999;
		while (index < len) {
			int b;
			int shift = 0;
			int result = 0;
			do {
				b = encodedPolyline.charAt(index++) - 63;
				result |= (b & 0x1f) << shift;
				shift += 5;
			} while (b >= 0x20);
			int dlat = (((result & 1) != 0) ? ~(result >> 1) : (result >> 1));
			lat += dlat;

			shift = 0;
			result = 0;
			do {
				b = encodedPolyline.charAt(index++) - 63;
				result |= (b & 0x1f) << shift;
				shift += 5;
			} while (b >= 0x20);
			int dlng = (((result & 1) != 0) ? ~(result >> 1) : (result >> 1));
			lng += dlng;
			minlat = Math.min(minlat, lat);
			minlng = Math.min(minlng, lng);
			maxlat = Math.max(maxlat, lat);
			maxlng = Math.max(maxlng, lng);
			float flat = ((float)lat)/100000;
			float flng = ((float)lng)/100000;
			if(lflat > -999) {
				dist += MapUtils.getDistance(flat, flng, lflat, lflng);
				if(dist > 9999)
					if((int)(dist/10000) != (int)(ldist/10000)) {
						markerPoints.add(new SimpleTrackpoint(flat,flng));
						distancePoints.add(dist);
					}				
			}
			ldist = dist;
			lflat = flat;
			lflng = flng;
			trackPoints.add(new SimpleTrackpoint(flat,flng));
		}
		bound1Lat = ((float)minlat)/100000;
		bound1Lng = ((float)minlng)/100000;
		bound2Lat = ((float)maxlat)/100000;
		bound2Lng = ((float)maxlng)/100000;
	}

	private void decodeLevels(String encodedLevels) {
		for (int i = 0; i < encodedLevels.length(); ++i) {
			Integer level = encodedLevels.charAt(i) - 63;
			levels.add(level);
		}
	}

	public String getColor() {
		return color;
	}
}
